#include <iostream>
#include <cmath>
#include <climits>
#include <cstdlib>
#include <ctime>
#include <cstdio>
#include <cstring>

#ifdef _WIN32
#include <mpi.h>
#else
#include <mpich2/mpi.h>
#endif

#include "MAMC.h"
#include "MAMC_TestCases.h"

using namespace std;

/* Command-line program za testiranje Monte Carlo integracije */
int main(int argc, char* argv[]) {
	/* Defaultni testni primer je racunanje PI pomocu kruga */
	MAMC_TestCase tcase = TC_PI2D;
	/* Defaultni metod integracije je adaptivni */
	IntegrationMethod im = IM_AdaptiveMC;

	ULL samples = 1000;
	ULL samplesTaken = 0, samplesTotal = 0;
	LD lmean, lvar, lerr, mean, var, err;
	double startTime;
	int needHelp = 0, invalid = 0, rank, size, echo = 1, batch = 0;

	/* Inicijalizacija MPI biblioteke */
	MPI_Init(&argc, &argv);
	MPI_Comm_size(MPI_COMM_WORLD, &size);
	MPI_Comm_rank(MPI_COMM_WORLD, &rank);

	for(int i = 1; i < argc; ++i)
	{
		int temp;

		/* Parsovanje argumenata s komandne linije */
		if(strcmp(argv[i], "--regular") == 0 || strcmp(argv[i], "-r") == 0)
			im = IM_Uniform;
		else if(strcmp(argv[i], "--mcarlo") == 0 || strcmp(argv[i], "-m") == 0)
			im = IM_MonteCarlo;
		else if(strcmp(argv[i], "--adaptive") == 0 || strcmp(argv[i], "-a") == 0)
			im = IM_AdaptiveMC;
		else if(strcmp(argv[i], "--batch") == 0 || strcmp(argv[i], "-b") == 0)
			batch = 1;
		else if(strcmp(argv[i], "--silent") == 0)
			echo = 0;
		else if(strcmp(argv[i], "--sin0pi") == 0)
			tcase = TC_Sin0PI;
		else if(strcmp(argv[i], "--pi1d") == 0)
			tcase = TC_PI1D;
		else if(strcmp(argv[i], "--pi2d") == 0)
			tcase = TC_PI2D;
		else if((strcmp(argv[i], "--samples") == 0 || strcmp(argv[i], "-s") == 0) && i < argc - 1)
			sscanf(argv[++i], "%Ld", &samples);
		else if(strcmp(argv[i], "--adSteps") == 0)
			{ sscanf(argv[++i], "%d", &temp); setAdaptiveSteps(temp); }
		else if(strcmp(argv[i], "--help") == 0)
			needHelp = 1;
		else
			invalid = needHelp = 1;
	}

	/* Pomoc, ako se zatrazi ili ako je neki argument pogresan */
	if(needHelp)
	{
		if(rank == 0)
		{
			if(invalid)
				cout << "Neki argumenti su neispravni!" << endl << endl;
			else
				cout << "Pomoc : " << endl << endl;

			cout << "--help                ovaj tekst" << endl;
			cout << "-r, --regular         pravilna raspodela uzoraka" << endl;
			cout << "-m, --mcarlo          osnovni Monte Carlo" << endl;
			cout << "-a, --adaptive        adaptivni Monte Carlo" << endl << endl;
			cout << "--sin0pi              f(x) = sin(x) : 0 - pi" << endl;
			cout << "--pi1d                f(x) = sqrt(1 - x^2) : 0 - 1" << endl << endl;
			cout << "--pi2d                f(x) = sqrt(x^2 + y^2) <= 1.0 ? 1.0 : 0.0" << endl << endl;
			cout << "-s X, --samples X     ukupan broj uzoraka" << endl << endl;
			cout << "--adSteps X     	   broj koraka u adaptivnom algoritmu" << endl << endl;
		}
		MPI_Finalize();
		return 1;
	}

	/* Merenje vremena */
	startTime = MPI_Wtime();

	/* Da se ne bi isti set pseudoslucajnih brojeva koristio u razlicitim procesima,
	 * i rank procesa pomnozen ukupnim brojem procesa se ubacuje u seed. */
	srand(time(0) + rank * size);

	/* Sama integracija */
	lerr = integrateFunction(tcase, im, samples / size, lmean, lvar);
	samplesTaken = getSampleCount();

	/* Prikupljanje rezultata */
	MPI_Reduce(&lerr, &err, 1, MPI_LONG_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);
	MPI_Reduce(&lvar, &var, 1, MPI_LONG_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);
	MPI_Reduce(&lmean, &mean, 1, MPI_LONG_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);
	MPI_Reduce(&samplesTaken, &samplesTotal, 1, MPI_UNSIGNED_LONG_LONG, MPI_SUM, 0, MPI_COMM_WORLD);

	/* Nulti proces ispisuje rezultate */
	if(rank == 0 && echo)
	{
		mean /= size;

		if(batch)
		{
			printf("%.10Lf\n", mean);
		}
		else
		{
			printf("Result : %.10Lf\n", mean);
			printf("Error : %.10Lf\n", mean - tcase.result);
			printf("Time elapsed : %.3lf\n", MPI_Wtime() - startTime);
			printf("Samples taken : %Ld\n", samplesTotal);
		}
	}

	MPI_Finalize();
	return 0;
}
